package com.my.chapter19;

import net.mindview.util.Generator;

import java.util.*;
import java.util.concurrent.locks.ReentrantLock;

public class practice10 {
    public static void main(String[] args) {
        for(final VendingMachine2.Machine m :
                VendingMachine2.Machine.values()) {
            Generator<Input> gen = new RandomInputGenerator();
            final Generator<Input> g = gen;
            new Thread(new Runnable() {
                public void run() { VendingMachine2.run(g, m); }
            }).start();
        }
    }
}

class VendingMachine2 {
    private static class Context {
        private State state = State.RESTING;
        private int amount;
        private Input selection;
    }
    private static Map<Machine,Context> em =
            Collections.synchronizedMap(
                    new EnumMap<Machine,Context>(Machine.class));
    static {
        for(Machine m : Machine.values())
            em.put(m, new Context());
    }
    enum Machine { M1, M2, M3 }
    private static final ReentrantLock lock =
            new ReentrantLock();
    private static State state;
    private static int amount;
    private static Input selection;
    enum StateDuration { TRANSIENT }
    enum State {
        RESTING {
            void next(Input input) {
                switch(Category.categorize(input)) {
                    case MONEY:
                        amount += input.amount();
                        state = ADDING_MONEY;
                        break;
                    case SHUT_DOWN:
                        state = TERMINAL;
                    default:
                }
            }
        },
        ADDING_MONEY {
            void next(Input input) {
                switch(Category.categorize(input)) {
                    case MONEY:
                        amount += input.amount();
                        break;
                    case ITEM_SELECTION:
                        selection = input;
                        if(amount < selection.amount())
                            System.out.println("Insufficient money for " + selection);
                        else state = DISPENSING;
                        break;
                    case QUIT_TRANSACTION:
                        state = GIVING_CHANGE;
                        break;
                    case SHUT_DOWN:
                        state = TERMINAL;
                    default:
                }
            }
        },
        DISPENSING(StateDuration.TRANSIENT) {
            void next() {
                System.out.println("here is your " + selection);
                amount -= selection.amount();
                state = GIVING_CHANGE;
            }
        },
        GIVING_CHANGE(StateDuration.TRANSIENT) {
            void next() {
                if(amount > 0) {
                    System.out.println("Your change: " + amount);
                    amount = 0;
                }
                state = RESTING;
            }
        },
        TERMINAL { void output() { System.out.println("Halted"); } };
        private boolean isTransient = false;
        State() {}
        State(StateDuration trans) { isTransient = true; }
        void next(Input input) {
            throw new RuntimeException("Only call " +
                    "next(Input input) for non-transient states");
        }
        void next() {
            throw new RuntimeException("Only call next() for " +
                    "StateDuration.TRANSIENT states");
        }
        void output() { System.out.println(amount); }

    }
    static void run(Generator<Input> gen, Machine m) {
        Context ctx = em.get(m);
        while(ctx.state != State.TERMINAL) {
            lock.lock();
            state = ctx.state;
            amount = ctx.amount;
            selection = ctx.selection;
            try {
                state.next(gen.next());
                while(state.isTransient)
                    state.next();
                state.output();
                ctx.state = state;
                ctx.amount = amount;
                ctx.selection = selection;
                em.put(m, ctx);
            } finally {
                lock.unlock();
            }
            Thread.yield();
        }
    }
}

enum Input{
    NICKEL(5),DIME(10),QUARTER(25),DOLLAR(100),
    TOOTHPASTE(200),CHIPS(75),SODA(100),SOAP(50),
    ABORT_TRANSACTION{
        public int amount(){
            throw new RuntimeException("ABORT.amount()");
        }
    },
    STOP{
        public int amount(){
            throw new RuntimeException("SHUT_DOWN.amount()");
        }
    };
    int value;
    Input(int value){
        this.value = value;
    }
    Input(){}
    int amount(){ return value; }
    static Random rand = new Random(47);
    public static Input randomSelection(){
        return values()[rand.nextInt(values().length-1)];
    }
}

enum Category{
    MONEY(Input.NICKEL, Input.DIME, Input.QUARTER,Input.DOLLAR),
    ITEM_SELECTION(Input.TOOTHPASTE,Input.CHIPS,Input.SODA,Input.SOAP),
    QUIT_TRANSACTION(Input.ABORT_TRANSACTION),
    SHUT_DOWN(Input.STOP);
    private Input[] values;
    Category(Input... types){ values = types; }
    private static EnumMap<Input,Category> categories =
            new EnumMap<Input, Category>(Input.class);
    static {
        for(Category c: Category.class.getEnumConstants()){
            for(Input type: c.values){
                categories.put(type,c);
            }
        }
    }
    public static Category categorize(Input input){
        return categories.get(input);
    }
}

class RandomInputGenerator implements Generator<Input>{

    @Override
    public Input next() {
        return Input.randomSelection();
    }
}



